package v1

import (
	"context"
	"encoding/json"
	"log/slog"
	"quipus/internal/database/model"
	"quipus/internal/utils"
	v1pb "quipus/proto/gen/api/v1"
	apb "quipus/proto/gen/assist"
	cpb "quipus/proto/gen/common"
	"time"

	"github.com/google/cel-go/cel"
	"github.com/pkg/errors"
	expr "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/types/known/emptypb"
)

func (s *APIV1Service) CreateSubject(ctx context.Context, request *v1pb.CreateSubjectRequest) (*v1pb.Subject, error) {
	currentUser, err := s.GetCurrentUser(ctx)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
	}

	subject := convertSubjectToDB(request)
	subject.Visibility = cpb.Visibility_TENANT.String()
	if request.Granaries != nil || len(request.Granaries) > 1 {
		emid := int32(0)
		for _, g := range request.Granaries {
			id, err := utils.ExtractIDFromUrn(utils.GranaryUrnPrefix, g)
			if err != nil {
				return nil, status.Errorf(codes.Internal, "failed to extract id from urn: %v", err)
			}
			granary, err := s.Gds.Handler.GetGranary(ctx, id)
			if emid == 0 {
				emid = granary.EmbeddingModelID
			} else {
				if emid != granary.EmbeddingModelID {
					slog.Error("granaries must have the same embedding model")
					return nil, status.Errorf(codes.Internal, "granaries must have the same embedding model")
				}
			}
		}
		cb, e := json.Marshal(request.Granaries)
		if e != nil {
			return nil, status.Errorf(codes.Internal, "failed to marshal granaries: %v", err)
		}
		subject.Granaries = string(cb)
	}

	if request.DefaultResponder != "" {
		userId, err := utils.ExtractIDFromUrn(utils.UserUrnPrefix, request.DefaultResponder)
		if err != nil {
			return nil, status.Errorf(codes.InvalidArgument, "invalid urn: %v", err)
		}
		subject.DefaultResponder = userId
	}

	subject.TenantID = currentUser.TenantID
	subject.CreatorID = currentUser.ID
	err = s.Gds.Handler.CreateSubject(ctx, subject)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to create subject, error: %+v", err)
	}

	subjectMsg, err := s.convertSubjectFromDB(ctx, subject, currentUser.ID)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to convert subject, error: %+v", err)
	}

	createTopic := func() {

		topicRequset := &v1pb.CreateTopicRequest{
			SubjectUrn:      subjectMsg.Urn,
			UseGranaryTopic: true,
		}

		nctx, _ := UserCtxClone(ctx)
		_, err := s.CreateTopic(nctx, topicRequset)
		if err != nil {
			slog.Error("failed to create topic", slog.Any("error", err))
		}
	}

	go createTopic()
	return subjectMsg, nil
}

func convertSubjectToDB(req *v1pb.CreateSubjectRequest) *model.Subject {
	temp := &model.Subject{
		Matter:      req.Matter,
		AvatarURL:   req.Avatar,
		Description: req.Description,
		RagMode:     req.RagMode.String(),
	}

	if req.Granaries != nil || len(req.Granaries) > 1 {
		cb, e := json.Marshal(req.Granaries)
		if e != nil {
			return nil
		}
		temp.Granaries = string(cb)
	}
	return temp
}

func (s *APIV1Service) DeleteSubject(ctx context.Context, request *v1pb.DeleteSubjectRequest) (*emptypb.Empty, error) {
	currentUser, err := s.GetCurrentUser(ctx)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
	}

	id, err := utils.ExtractIDFromUrn(utils.SubjectUrnPrefix, request.Urn)
	if err != nil {
		return nil, status.Errorf(codes.InvalidArgument, "invalid urn: %v", err)
	}

	subject, err := s.Gds.Handler.GetSubjectById(ctx, id)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get subject, error: %+v", err)
	}

	if subject == nil {
		return nil, status.Errorf(codes.NotFound, "subject not found")
	}

	if currentUser.Role != cpb.Role_HOST.String() && currentUser.Role != cpb.Role_ADMIN.String() {
		if subject.TenantID != currentUser.TenantID {
			return nil, status.Errorf(codes.PermissionDenied, "permission denied")
		}

		if subject.CreatorID == currentUser.ID || currentUser.Role == cpb.Role_TENANT_ADMIN.String() {
			slog.Info("delete subject", "subject", subject.Matter)
		} else {
			return nil, status.Errorf(codes.PermissionDenied, "permission denied")
		}
	}

	err = s.Gds.Handler.DeleteTopic(ctx, &apb.DeleteTopic{SubjectId: id})
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to delete topic, error: %+v", err)
	}

	_, err = s.Gds.Handler.UnFavorSubject(ctx, 0, id)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to unfavorite subject, error: %+v", err)
	}

	err = s.Gds.Handler.DeleteSubject(ctx, id)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to delete llm subject, error: %+v", err)
	}
	return nil, nil
}

func (s *APIV1Service) DeleteSubjectTopics(ctx context.Context, request *v1pb.DeleteSubjectTopicsRequest) (*emptypb.Empty, error) {
	currentUser, err := s.GetCurrentUser(ctx)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
	}

	id, err := utils.ExtractIDFromUrn(utils.SubjectUrnPrefix, request.Urn)
	if err != nil {
		return nil, status.Errorf(codes.InvalidArgument, "invalid urn: %v", err)
	}

	subject, err := s.Gds.Handler.GetSubjectById(ctx, id)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get subject, error: %+v", err)
	}

	if subject == nil {
		return nil, status.Errorf(codes.NotFound, "subject not found")
	}

	if currentUser.Role != cpb.Role_HOST.String() && currentUser.Role != cpb.Role_ADMIN.String() {
		if subject.TenantID != currentUser.TenantID {
			return nil, status.Errorf(codes.PermissionDenied, "permission denied")
		}

		if subject.CreatorID == currentUser.ID || currentUser.Role == cpb.Role_TENANT_ADMIN.String() {
			slog.Info("delete subject", "subject", subject.Matter)
		} else {
			return nil, status.Errorf(codes.PermissionDenied, "permission denied")
		}
	}

	err = s.Gds.Handler.DeleteTopic(ctx, &apb.DeleteTopic{SubjectId: id})
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to delete topic, error: %+v", err)
	}

	return nil, nil
}

func (s *APIV1Service) UpdateSubject(ctx context.Context, request *v1pb.UpdateSubjectRequest) (*v1pb.Subject, error) {
	currentUser, err := s.GetCurrentUser(ctx)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
	}

	id, err := utils.ExtractIDFromUrn(utils.SubjectUrnPrefix, request.Subject.Urn)
	if err != nil {
		return nil, status.Errorf(codes.InvalidArgument, "invalid urn: %v", err)
	}

	subject, err := s.Gds.Handler.GetSubjectById(ctx, id)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get subject, error: %+v", err)
	}

	if currentUser.Role != cpb.Role_HOST.String() && currentUser.Role != cpb.Role_ADMIN.String() {
		if subject.TenantID != currentUser.TenantID {
			return nil, status.Errorf(codes.PermissionDenied, "permission denied")
		}

		if subject.CreatorID == currentUser.ID || currentUser.Role == cpb.Role_TENANT_ADMIN.String() {
			slog.Info("delete subject", "subject", subject.Matter)
		} else {
			return nil, status.Errorf(codes.PermissionDenied, "permission denied")
		}
	}

	updateGranary := false
	for _, field := range request.UpdateMask.Paths {
		if field == "matter" {
			subject.Matter = request.Subject.Matter
		} else if field == "description" {
			subject.Description = request.Subject.Description
		} else if field == "granaries" {
			b, e := json.Marshal(request.Subject.Granaries)
			if e != nil {
				return nil, status.Errorf(codes.Internal, "failed to marshal granaries, error: %+v", e)
			}
			updateGranary = true
			subject.Granaries = string(b)
		} else if field == "avatar" {
			subject.AvatarURL = request.Subject.Avatar
		} else if field == "responder" {
			userid, err := utils.ExtractIDFromUrn(utils.UserUrnPrefix, request.Subject.DefaultResponder)
			if err != nil {
				return nil, status.Errorf(codes.InvalidArgument, "invalid update path: %s", field)
			}
			subject.DefaultResponder = userid
		} else {
			return nil, status.Errorf(codes.InvalidArgument, "invalid update path: %s", field)
		}
	}

	subject.UpdatedTs = time.Now().Unix()

	_, err = s.Gds.Handler.UpdateSubject(ctx, subject)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to delete topic, error: %+v", err)
	}

	subjectMsg, err := s.convertSubjectFromDB(ctx, subject, currentUser.ID)
	updateTopic := func() {
		nctx, _ := UserCtxClone(ctx)
		deleteRequest := &v1pb.DeleteSubjectTopicsRequest{
			Urn: subjectMsg.Urn,
		}

		_, err := s.DeleteSubjectTopics(nctx, deleteRequest)
		if err != nil {
			slog.Error("failed to delete topic", slog.Any("error", err))
		}

		topicRequset := &v1pb.CreateTopicRequest{
			SubjectUrn:      subjectMsg.Urn,
			UseGranaryTopic: true,
		}
		_, err = s.CreateTopic(nctx, topicRequset)
		if err != nil {
			slog.Error("failed to create topic", slog.Any("error", err))
		}
	}
	if updateGranary {
		go updateTopic()
	}
	return subjectMsg, nil
}

func (s *APIV1Service) DeleteTopic(ctx context.Context, request *v1pb.DeleteTopicRequest) (*emptypb.Empty, error) {
	currentUser, err := s.GetCurrentUser(ctx)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
	}

	id, err := utils.ExtractIDFromUrn(utils.TopicUrnPrefix, request.Urn)
	if err != nil {
		return nil, status.Errorf(codes.InvalidArgument, "invalid urn: %v", err)
	}

	topic, err := s.Gds.Handler.GetTopic(ctx, id)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get topic, error: %+v", err)
	}

	if currentUser.Role != cpb.Role_HOST.String() && currentUser.Role != cpb.Role_ADMIN.String() {
		if topic.TenantID != currentUser.TenantID {
			return nil, status.Errorf(codes.PermissionDenied, "permission denied")
		}

		if topic.CreatorID == currentUser.ID || currentUser.Role == cpb.Role_TENANT_ADMIN.String() {
			slog.Info("delete subject", "topic", topic.ID)
		} else {
			return nil, status.Errorf(codes.PermissionDenied, "permission denied")
		}
	}

	err = s.Gds.Handler.DeleteTopic(ctx, &apb.DeleteTopic{TopicId: id})
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to delete topic, error: %+v", err)
	}
	return nil, nil
}

func (s *APIV1Service) GetSubject(ctx context.Context, request *v1pb.GetSubjectRequest) (*v1pb.Subject, error) {
	currentUser, err := s.GetCurrentUser(ctx)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
	}

	id, err := utils.ExtractIDFromUrn(utils.SubjectUrnPrefix, request.Urn)
	if err != nil {
		return nil, status.Errorf(codes.InvalidArgument, "invalid urn: %v", err)
	}

	subject, err := s.Gds.Handler.GetSubjectById(ctx, id)

	if currentUser.TenantID > 0 && subject.TenantID != currentUser.TenantID {
		return nil, status.Errorf(codes.PermissionDenied, "permission denied")
	}

	subjectMsg, err := s.convertSubjectFromDB(ctx, subject, subject.CreatorID)
	u, _ := s.Gds.Handler.GetUser(ctx, &apb.FindUser{Id: subject.CreatorID})
	if u == nil {
		return subjectMsg, nil
	}
	subjectMsg.Creator = s.convertUserFromStore(u)
	return subjectMsg, nil
}

func (s *APIV1Service) ListSubjects(ctx context.Context, request *v1pb.ListSubjectsRequest) (*v1pb.ListSubjectsResponse, error) {
	currentUser, err := s.GetCurrentUser(ctx)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
	}

	subjectFind := &apb.FindSubject{}
	buildSubjectFindWithFilter(subjectFind, request.Filter)

	var limit, offset int32

	if request.PageToken != nil {
		limit = request.PageToken.Limit
		offset = request.PageToken.Offset
	} else {
		limit = request.PageSize
	}

	if limit <= 0 {
		limit = DefaultPageSize
	}

	limitPlusOne := limit
	subjectFind.Limit = limitPlusOne
	subjectFind.Offset = offset
	if currentUser.Role != cpb.Role_HOST.String() && currentUser.Role != cpb.Role_ADMIN.String() {
		subjectFind.TenantId = currentUser.TenantID
	}

	subjects, count, err := s.Gds.Handler.ListSubjects(ctx, subjectFind)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to list subject types: %v", err)
	}
	subjectMessages := []*v1pb.Subject{}

	if int32(len(subjects)) == limitPlusOne {
		subjects = subjects[:limit]
	}

	user, err := s.GetCurrentUser(ctx)
	userId := int32(0)
	if err == nil && user != nil {
		userId = user.ID
	}

	userMap := make(map[int32]*v1pb.User)
	for _, subject := range subjects {
		subjectMsg, err := s.convertSubjectFromDB(ctx, subject, userId)
		if err != nil {
			return nil, errors.Wrap(err, "failed to convert subject")
		}
		user := userMap[subject.CreatorID]
		if user == nil {
			u, _ := s.Gds.Handler.GetUser(ctx, &apb.FindUser{Id: subject.CreatorID})
			if u == nil {
				continue
			}
			user = s.convertUserFromStore(u)

			if err != nil {
				return nil, errors.Wrap(err, "failed to convert user")
			}
		}

		subjectMsg.Creator = user

		userMap[subject.CreatorID] = user

		subjectMessages = append(subjectMessages, subjectMsg)
	}

	response := &v1pb.ListSubjectsResponse{
		TotalSize: count,
		Subjects:  subjectMessages,
		NextPageToken: &cpb.PageToken{
			Limit:  int32(limit),
			Offset: int32(offset + limit),
		},
	}

	return response, nil
}

func convertTopicToDB(topicdefs []*cpb.TopicDef, user *model.User, subjectId int32) ([]*model.Topic, map[string][]*model.Topic) {
	tmpTopics := []*model.Topic{}
	unitMap := make(map[string][]*model.Topic)
	for _, topic := range topicdefs {
		temp := &model.Topic{
			Title:     topic.Primary.Title,
			Summary:   topic.Primary.Summary,
			SubjectID: subjectId,
			CreatorID: user.ID,
			TenantID:  user.TenantID,
		}
		tmpTopics = append(tmpTopics, temp)
		h := ""
		if len(topic.Units) > 0 {
			h = utils.HashString(temp.Title + temp.Summary)
			unitTmpTopics := unitMap[h]
			if unitTmpTopics == nil {
				unitTmpTopics = []*model.Topic{}
			}
			for _, v := range topic.Units {
				uTemp := &model.Topic{
					Title:     v.Title,
					Summary:   v.Summary,
					SubjectID: subjectId,
					CreatorID: user.ID,
					TenantID:  user.TenantID,
				}
				unitTmpTopics = append(unitTmpTopics, uTemp)
			}
			unitMap[h] = unitTmpTopics
		}
	}
	return tmpTopics, unitMap
}

func (s *APIV1Service) CreateTopic(ctx context.Context, request *v1pb.CreateTopicRequest) (*v1pb.CreateTopicResponse, error) {
	currentUser, err := s.GetCurrentUser(ctx)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
	}

	subjectId, err := utils.ExtractIDFromUrn(utils.SubjectUrnPrefix, request.SubjectUrn)
	if err != nil {
		return nil, status.Errorf(codes.InvalidArgument, "invalid subject urn: %v", err)
	}

	subject, err := s.Gds.Handler.GetSubjectById(ctx, subjectId)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get subject: %v", err)
	}

	if currentUser.Role != cpb.Role_HOST.String() && currentUser.Role != cpb.Role_ADMIN.String() {
		if subject.TenantID != currentUser.TenantID {
			return nil, status.Errorf(codes.PermissionDenied, "permission denied")
		}

		if subject.CreatorID == currentUser.ID || currentUser.Role == cpb.Role_TENANT_ADMIN.String() {
			slog.Info("create topic", "subject", subject.Matter)
		} else {
			return nil, status.Errorf(codes.PermissionDenied, "permission denied")
		}
	}

	var topics []*model.Topic
	var uniMap map[string][]*model.Topic
	topicdefs := request.Topics
	if request.UseGranaryTopic == true && subject.Granaries != "{}" {
		granaries := []string{}
		err = json.Unmarshal([]byte(subject.Granaries), &granaries)
		if err != nil {
			return nil, status.Errorf(codes.Internal, "failed to unmarshal granaries: %v", err)
		}
		for _, granary := range granaries {
			topicrsp, err := s.ListGranariesTopics(ctx, &v1pb.ListGranariesTopicsRequest{
				Urn: granary,
			})
			if err != nil {
				return nil, status.Errorf(codes.Internal, "failed to get granary topic: %v", err)
			}
			topicdefs = append(topicdefs, topicrsp.Topics...)
		}
	}

	topics, uniMap = convertTopicToDB(topicdefs, currentUser, subjectId)
	err = s.Gds.Handler.CreateTopicInBatchs(ctx, topics)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to create subject, error: %+v", err)
	}
	if uniMap != nil {
		tmpTopics := []*model.Topic{}

		for _, v := range topics {
			h := utils.HashString(v.Title + v.Summary)
			units := uniMap[h]
			if units != nil {
				for _, u := range units {
					u.ParentID = v.ID
					tmpTopics = append(tmpTopics, u)
				}
			}

		}
		err = s.Gds.Handler.CreateTopicInBatchs(ctx, tmpTopics)
		if err != nil {
			return nil, status.Errorf(codes.Internal, "failed to create subject, error: %+v", err)
		}
		topics = append(topics, tmpTopics...)
	}
	topicMessages, err := convertTopicListFromDB(topics)
	if err != nil {
		return nil, errors.Wrap(err, "failed to convert topic")
	}

	response := &v1pb.CreateTopicResponse{
		Topics: topicMessages,
	}

	return response, nil
}

func (s *APIV1Service) GetTopic(ctx context.Context, request *v1pb.GetTopicRequest) (*cpb.TopicDef, error) {
	currentUser, err := s.GetCurrentUser(ctx)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
	}

	id, err := utils.ExtractIDFromUrn(utils.TopicUrnPrefix, request.Urn)
	if err != nil {
		return nil, status.Errorf(codes.InvalidArgument, "invalid urn: %v", err)
	}

	topics := []*model.Topic{}
	topic, err := s.Gds.Handler.GetTopic(ctx, id)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get topic, error: %+v", err)
	}

	if currentUser.Role != cpb.Role_HOST.String() && currentUser.Role != cpb.Role_ADMIN.String() {
		if topic.TenantID != currentUser.TenantID {
			return nil, status.Errorf(codes.PermissionDenied, "permission denied")
		}

	}
	topics = append(topics, topic)
	topicUnits, err := s.Gds.Handler.GetTopicUnits(ctx, id)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get topic unit, error: %+v", err)
	}
	topics = append(topics, topicUnits...)
	topicMessages, err := convertTopicListFromDB(topics)
	if err != nil {
		return nil, errors.Wrap(err, "failed to convert topic")
	}

	return topicMessages[0], nil
}

func (s *APIV1Service) ListTopics(ctx context.Context, request *v1pb.ListTopicsRequest) (*v1pb.ListTopicsResponse, error) {
	currentUser, err := s.GetCurrentUser(ctx)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
	}

	topicFind := &apb.FindTopic{}
	subjectId, err := utils.ExtractIDFromUrn(utils.SubjectUrnPrefix, request.SubjectUrn)
	if err != nil {
		return nil, status.Errorf(codes.InvalidArgument, "invalid subject urn: %v", err)
	}

	subject, err := s.Gds.Handler.GetSubjectById(ctx, subjectId)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get subject: %v", err)
	}

	if currentUser.Role != cpb.Role_HOST.String() && currentUser.Role != cpb.Role_ADMIN.String() {
		if subject.TenantID != currentUser.TenantID {
			return nil, status.Errorf(codes.PermissionDenied, "permission denied")
		}
	}

	buildTopicFindWithFilter(topicFind, request.Filter)
	topicFind.SubjectId = subjectId

	var limit, offset int32
	if request.PageToken != nil {
		limit = request.PageToken.Limit
		offset = request.PageToken.Offset
	} else {
		limit = request.PageSize
	}
	if limit <= 0 {
		limit = DefaultPageSize
	}
	limitPlusOne := limit
	topicFind.Limit = limitPlusOne
	topicFind.Offset = offset
	topics, count, err := s.Gds.Handler.ListTopics(ctx, topicFind)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to list knots: %v", err)
	}

	if int32(len(topics)) == limitPlusOne {
		topics = topics[:limit]
		// nextPageToken, err = getPageToken(limit, offset+limit)
		if err != nil {
			return nil, status.Errorf(codes.Internal, "failed to get next page token, error: %v", err)
		}
	}

	topicMessages, err := convertTopicListFromDB(topics)

	if err != nil {
		return nil, errors.Wrap(err, "failed to convert topic")
	}

	response := &v1pb.ListTopicsResponse{
		TotalSize: count,
		Topics:    topicMessages,
		NextPageToken: &cpb.PageToken{
			Limit:  int32(limit),
			Offset: int32(offset + limit),
		},
	}
	return response, nil
}

func (s *APIV1Service) ListFavorSubjects(ctx context.Context, request *v1pb.ListFavorSubjectsRequest) (*v1pb.ListSubjectsResponse, error) {
	user, err := s.GetCurrentUser(ctx)

	subjectFind := &apb.FindSubject{}
	var limit, offset int32
	if request.PageToken != nil {
		limit = request.PageToken.Limit
		offset = request.PageToken.Offset
	} else {
		limit = request.PageSize
	}
	if limit <= 0 {
		limit = DefaultPageSize
	}
	limitPlusOne := limit
	subjectFind.Limit = limitPlusOne
	subjectFind.Offset = offset
	subjectFind.CurrentUserId = user.ID

	subjects, count, err := s.Gds.Handler.ListFavorSubjects(ctx, subjectFind)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to list topic types: %v", err)
	}
	subjectMessages := []*v1pb.Subject{}

	if int32(len(subjects)) == limitPlusOne {
		subjects = subjects[:limit]
		// nextPageToken, err = getPageToken(limit, offset+limit)
		if err != nil {
			return nil, status.Errorf(codes.Internal, "failed to get next page token, error: %v", err)
		}
	}

	for _, subject := range subjects {
		subjectMsg, err := s.convertSubjectFromDB(ctx, subject, user.ID)
		if err != nil {
			return nil, errors.Wrap(err, "failed to convert subject")
		}
		subjectMessages = append(subjectMessages, subjectMsg)
	}

	response := &v1pb.ListSubjectsResponse{
		TotalSize: count,
		Subjects:  subjectMessages,
		NextPageToken: &cpb.PageToken{
			Limit:  int32(limit),
			Offset: int32(offset + limit),
		},
	}

	return response, nil
}

func (s *APIV1Service) SetFavorSubject(ctx context.Context, request *v1pb.SetFavorSubjectRequest) (*v1pb.Subject, error) {
	user, err := s.GetCurrentUser(ctx)
	if err != nil {
		return nil, err
	}

	subjectId, err := utils.ExtractIDFromUrn(utils.SubjectUrnPrefix, request.Urn)

	var subject *model.Subject
	if request.IsFavor == false {
		subject, err = s.Gds.Handler.UnFavorSubject(ctx, user.ID, subjectId)
	}

	if request.IsFavor == true {
		subject, err = s.Gds.Handler.FavorSubject(ctx, user.ID, subjectId)
	}

	return s.convertSubjectFromDB(ctx, subject, user.ID)
}

func convertTopicFromDB(topic *model.Topic) *cpb.TopicUnit {
	topicMsg := &cpb.TopicUnit{
		Title:   topic.Title,
		Urn:     utils.BuildUrn(utils.TopicUrnPrefix, topic.ID),
		Summary: topic.Summary,
	}

	return topicMsg
}

func convertTopicListFromDB(topics []*model.Topic) ([]*cpb.TopicDef, error) {
	topicMap := make(map[string]*cpb.TopicDef)
	for _, topic := range topics {
		topicUnit := convertTopicFromDB(topic)
		var topicDef *cpb.TopicDef
		if topic.ParentID == 0 {
			topicDef = topicMap[topicUnit.Urn]
			if topicDef == nil {
				topicDef = &cpb.TopicDef{
					Primary: topicUnit,
				}
			}

			topicMap[topicUnit.Urn] = topicDef
		} else {
			pUrn := utils.BuildUrn(utils.TopicUrnPrefix, topic.ParentID)
			topicDef = topicMap[pUrn]
			if topicDef == nil {
				topicDef = &cpb.TopicDef{
					Units: []*cpb.TopicUnit{topicUnit},
				}
			} else {
				topicDef.Units = append(topicDef.Units, topicUnit)
			}

			topicMap[pUrn] = topicDef
		}
	}
	topicMsg := []*cpb.TopicDef{}
	for _, topic := range topicMap {
		topicMsg = append(topicMsg, topic)
	}
	return topicMsg, nil
}

func (s *APIV1Service) convertSubjectFromDB(ctx context.Context, subject *model.Subject, userId int32) (*v1pb.Subject, error) {
	subjectMsg := &v1pb.Subject{
		Matter:      subject.Matter,
		Urn:         utils.BuildUrn(utils.SubjectUrnPrefix, subject.ID),
		Avatar:      subject.AvatarURL,
		Description: subject.Description,
		Tenant:      utils.BuildUrn(utils.TenantUrnPrefix, subject.TenantID),
		RagMode:     cpb.RagMode(cpb.RagMode_value[subject.RagMode]),
	}

	if subject.DefaultResponder != 0 {
		subjectMsg.DefaultResponder = utils.BuildUrn(utils.UserUrnPrefix, subject.DefaultResponder)
	}

	if userId != 0 {
		ret, err := s.Gds.Handler.CheckFavorSubject(ctx, userId, subject.ID)
		if err != nil {
			return nil, err
		}
		subjectMsg.IsFavor = ret
	}

	hot, err := s.Gds.Handler.CountSubjectFavors(ctx, subject.ID)
	if err != nil {
		return nil, err
	}
	subjectMsg.FavorCount = hot

	if subject.Granaries != "{}" {
		e := json.Unmarshal([]byte(subject.Granaries), &subjectMsg.Granaries)
		if e != nil {
			return nil, e
		}
	}
	return subjectMsg, nil
}

type TopicFilter struct {
	Keywords  []string
	SubjectId int32
}

type SubjectFilter struct {
	Keywords     []string
	Matter       string
	Visibilities []cpb.Visibility
}

var TopicFilterCELAttributes = []cel.EnvOption{
	cel.Variable("keywords", cel.ListType(cel.StringType)),
}

var SubjectFilterCELAttributes = []cel.EnvOption{
	cel.Variable("keywords", cel.ListType(cel.StringType)),
	cel.Variable("matter", cel.StringType),
	cel.Variable("visibilities", cel.ListType(cel.StringType)),
}

func parseTopicFilter(expression string) (*TopicFilter, error) {
	e, err := cel.NewEnv(TopicFilterCELAttributes...)
	if err != nil {
		return nil, err
	}
	ast, issues := e.Compile(expression)
	if issues != nil {
		return nil, errors.Errorf("found issue %v", issues)
	}
	filter := &TopicFilter{}
	expr, err := cel.AstToParsedExpr(ast)
	if err != nil {
		return nil, err
	}
	callExpr := expr.GetExpr().GetCallExpr()
	findTopicField(callExpr, filter)
	return filter, nil
}

func findTopicField(callExpr *expr.Expr_Call, filter *TopicFilter) {
	if len(callExpr.Args) == 2 {
		idExpr := callExpr.Args[0].GetIdentExpr()
		if idExpr != nil {
			if idExpr.Name == "keywords" {
				keywords := []string{}
				for _, expr := range callExpr.Args[1].GetListExpr().GetElements() {
					value := expr.GetConstExpr().GetStringValue()
					keywords = append(keywords, value)
				}
				filter.Keywords = keywords
			}
			return
		}
	}
	for _, arg := range callExpr.Args {
		callExpr := arg.GetCallExpr()
		if callExpr != nil {
			findTopicField(callExpr, filter)
		}
	}
}

func buildTopicFindWithFilter(find *apb.FindTopic, filter string) error {
	if find == nil {
		find = &apb.FindTopic{}
	}

	if filter != "" {
		filter, err := parseTopicFilter(filter)
		if err != nil {
			return status.Errorf(codes.InvalidArgument, "invalid filter: %v", err)
		}
		if len(filter.Keywords) > 0 {
			find.KeyWords = filter.Keywords
		}
		if filter.SubjectId > 0 {
			find.SubjectId = filter.SubjectId
		}
	}

	return nil
}

func parseSubjectFilter(expression string) (*SubjectFilter, error) {
	e, err := cel.NewEnv(SubjectFilterCELAttributes...)
	if err != nil {
		return nil, err
	}
	ast, issues := e.Compile(expression)
	if issues != nil {
		return nil, errors.Errorf("found issue %v", issues)
	}
	filter := &SubjectFilter{}
	expr, err := cel.AstToParsedExpr(ast)
	if err != nil {
		return nil, err
	}
	callExpr := expr.GetExpr().GetCallExpr()
	findSubjectField(callExpr, filter)
	return filter, nil
}

func findSubjectField(callExpr *expr.Expr_Call, filter *SubjectFilter) {
	if len(callExpr.Args) == 2 {
		idExpr := callExpr.Args[0].GetIdentExpr()
		if idExpr != nil {
			if idExpr.Name == "keywords" {
				keywords := []string{}
				for _, expr := range callExpr.Args[1].GetListExpr().GetElements() {
					value := expr.GetConstExpr().GetStringValue()
					keywords = append(keywords, value)
				}
				filter.Keywords = keywords
			} else if idExpr.Name == "matter" {
				filter.Matter = callExpr.Args[1].GetConstExpr().GetStringValue()
			} else if idExpr.Name == "visibilities" {
				visibilities := []cpb.Visibility{}
				for _, expr := range callExpr.Args[1].GetListExpr().GetElements() {
					value := expr.GetConstExpr().GetStringValue()
					visibilities = append(visibilities, cpb.Visibility(cpb.Visibility_value[value]))
				}
				filter.Visibilities = visibilities
			}
			return
		}
	}
	for _, arg := range callExpr.Args {
		callExpr := arg.GetCallExpr()
		if callExpr != nil {
			findSubjectField(callExpr, filter)
		}
	}
}

func buildSubjectFindWithFilter(find *apb.FindSubject, filter string) error {
	if find == nil {
		find = &apb.FindSubject{}
	}

	if filter != "" {
		filter, err := parseSubjectFilter(filter)
		if err != nil {
			return status.Errorf(codes.InvalidArgument, "invalid filter: %v", err)
		}
		if len(filter.Keywords) > 0 {
			find.KeyWords = filter.Keywords
		}
		if filter.Matter != "" {
			find.Matter = filter.Matter
		}
		if len(filter.Visibilities) > 0 {
			find.Visibilities = filter.Visibilities
		}
	}

	return nil
}
